package com.bugsnag.ohos;

import com.bugsnag.ohos.internal.ImmutableConfig;
import com.bugsnag.ohos.util.OhosLog;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * EventInternal
 *
 * @since 2021-07-01
 */
public class EventInternal implements JsonStream.Streamable, MetadataAware, UserAware {
    Throwable originalError;
    ImmutableConfig config;
    Metadata metadata;
    String groupingHash;
    List<Thread> threads;
    String apiKey;
    String context = null;
    List<Error> errors;
    Session session;
    AppWithState app;
    DeviceWithState device;
    List<Breadcrumb> breadcrumbs = new ArrayList<>();
    User user = new User(null, null, null);
    private boolean originalUnhandled;
    private Set<String> discardClasses;
    private Collection<String> projectPackages;
    private SeverityReason severityReason;

    /**
     * constructor
     *
     * @param originalError
     * @param config
     * @param severityReason
     */
    public EventInternal(Throwable originalError, ImmutableConfig config, SeverityReason severityReason) {
        this(originalError, config, severityReason, new Metadata());
    }

    /**
     * constructor
     *
     * @param originalError
     * @param config
     * @param severityReason
     * @param data
     */
    public EventInternal(Throwable originalError, ImmutableConfig config,
                         SeverityReason severityReason, Metadata data) {
        this.originalError = originalError;
        this.severityReason = severityReason;
        this.metadata = data.copy();
        threads = new ThreadState(originalError, getUnhandled(), config).getThreads();
        discardClasses = config.getDiscardClasses();
        projectPackages = config.getProjectPackages();
        if (originalError == null) {
            errors = new ArrayList<>();
        } else {
            errors = Error.createError(originalError, config.getProjectPackages(), config.getLogger());
        }
        apiKey = config.getApiKey();
    }

    public void setMetadata(Metadata data) {
        this.metadata = data;
    }

    public boolean getOriginalUnhandled() {
        return severityReason.originalUnhandled;
    }

    public void setOriginalUnhandled(Boolean originalUnhandled) {
        this.originalUnhandled = originalUnhandled;
    }

    public boolean isOriginalUnhandled() {
        return originalUnhandled;
    }

    public String getGroupingHash() {
        return groupingHash;
    }

    public void setGroupingHash(String groupingHash) {
        this.groupingHash = groupingHash;
    }

    public String getSeverityReasonType() {
        return severityReason.getSeverityReasonType();
    }

    /**
     * current event is anr or not
     *
     * @param event
     * @return is anr
     */
    protected boolean isAnr(Event event) {
        List<Error> errorList = event.getErrors();
        String errorClass = null;
        if (!errorList.isEmpty()) {
            Error error = errorList.get(0);
            errorClass = error.getErrorClass();
        }
        return "ANR".equals(errorClass);
    }

    public boolean getUnhandled() {
        return severityReason.getUnhandled();
    }

    /**
     * unhandled is enable
     *
     * @param unhandled
     */
    public void setUnhandled(boolean unhandled) {
        severityReason.setUnhandled(unhandled);
    }

    public List<Thread> getThreads() {
        return threads;
    }

    public void setThreads(List<Thread> threads) {
        this.threads = threads;
    }

    public String getApiKey() {
        return apiKey;
    }

    public void setApiKey(String apiKey) {
        this.apiKey = apiKey;
    }

    public Throwable getOriginalError() {
        return originalError;
    }

    public void setOriginalError(Throwable originalError) {
        this.originalError = originalError;
    }

    public ImmutableConfig getConfig() {
        return config;
    }

    public void setConfig(ImmutableConfig config) {
        this.config = config;
    }

    public SeverityReason getSeverityReason() {
        return severityReason;
    }

    public void setSeverityReason(SeverityReason severityReason) {
        this.severityReason = severityReason;
    }

    public String getContext() {
        return context;
    }

    public void setContext(String context) {
        this.context = context;
    }

    public Set<String> getDiscardClasses() {
        return discardClasses;
    }

    public void setDiscardClasses(Set<String> discardClasses) {
        this.discardClasses = discardClasses;
    }

    public Collection<String> getProjectPackages() {
        return projectPackages;
    }

    public void setProjectPackages(Collection<String> projectPackages) {
        this.projectPackages = projectPackages;
    }

    public List<Error> getErrors() {
        return errors;
    }

    public void setErrors(List<Error> errors) {
        this.errors = errors;
    }

    /**
     * get error types from stack frames
     *
     * @return error types
     */
    public Set<ErrorType> getErrorTypesFromStackframes() {
        Set<ErrorType> errorTypes = new HashSet<>();
        Set<ErrorType> frameOverrideTypes = new HashSet<>();
        if (errors != null) {
            for (Error error : errors) {
                ErrorType type = error.getType();
                errorTypes.add(type);
            }
            for (Error error : errors) {
                List<Stackframe> stacktrace = error.getStacktrace();
                for (Stackframe stackframe : stacktrace) {
                    if (stackframe != null && stackframe.getType() != null) {
                        frameOverrideTypes.add(stackframe.getType());
                    }
                }
            }
        }
        errorTypes.addAll(frameOverrideTypes);
        return errorTypes;
    }

    public AppWithState getApp() {
        return app;
    }

    public void setApp(AppWithState app) {
        this.app = app;
    }

    public Session getSession() {
        return session;
    }

    public void setSession(Session session) {
        this.session = session;
    }

    public DeviceWithState getDevice() {
        return device;
    }

    public void setDevice(DeviceWithState device) {
        this.device = device;
    }

    /**
     * discard class is should be
     *
     * @return boolean
     */
    protected boolean shouldDiscardClass() {
        if (errors.isEmpty()) {
            return true;
        } else {
            for (Error error : errors) {
                if (discardClasses.contains(error.getErrorClass())) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * update Severity Internal
     *
     * @param severity
     */
    protected void updateSeverityInternal(Severity severity) {
        severityReason = SeverityReason.newInstance(
            severityReason.getSeverityReasonType(),
            severity,
            severityReason.getAttributeValue()
        );
        this.setSeverity(severity);
    }

    public List<Breadcrumb> getBreadcrumbs() {
        return breadcrumbs;
    }

    public void setBreadcrumbs(List<Breadcrumb> breadcrumbs) {
        this.breadcrumbs = breadcrumbs;
    }

    public Severity getSeverity() {
        return severityReason.getCurrentSeverity();
    }

    /**
     * set Severity
     *
     * @param severity
     */
    public void setSeverity(Severity severity) {
        severityReason.setCurrentSeverity(severity);
    }

    @Override
    public void toStream(JsonStream writer) throws IOException {
        OhosLog.d("EventInternal", "toStream start...%s", writer);
        writer.beginObject();
        writer.name("context").value(context);
        writer.name("metaData").value(metadata);
        writer.name("severity").value(getSeverity());
        writer.name("severityReason").value(severityReason);
        writer.name("unhandled").value(severityReason.getUnhandled());
        writer.name("exceptions");
        writer.beginArray();
        OhosLog.d("EventInternal", "toStream end..errors:%s", errors);
        for (Error error : errors) {
            writer.value(error);
        }
        writer.endArray();
        writer.name("projectPackages");
        writer.beginArray();
        if (projectPackages != null) {
            for (String projectPackage : projectPackages) {
                writer.value(projectPackage);
            }
        }
        writer.endArray();
        writer.name("user").value(user);
        writer.name("app").value(app);
        writer.name("device").value(device);
        writer.name("breadcrumbs").value(breadcrumbs);
        writer.name("groupingHash").value(groupingHash);

        writer.name("threads");
        writer.beginArray();
        for (Thread thread : threads) {
            writer.value(thread);
        }
        writer.endArray();

        if (session != null) {
            Session copy = Session.copySession(session);
            writer.name("session").beginObject();
            writer.name("id").value(copy.getId());
            writer.name("startedAt").value(copy.getStartedAt());
            writer.name("events").beginObject();
            writer.name("handled").value(copy.getHandledCount());
            writer.name("unhandled").value(copy.getUnhandledCount());
            writer.endObject();
            writer.endObject();
        }
        writer.endObject();
        OhosLog.d("EventInternal", "toStream end...");
    }

    @Override
    public void addMetadata(String section, Map<String, Object> value) {
        metadata.addMetadata(section, value);
    }

    @Override
    public void addMetadata(String section, String key, Object value) {
        metadata.addMetadata(section, key, value);
    }

    @Override
    public void clearMetadata(String section) {
        metadata.clearMetadata(section);
    }

    @Override
    public void clearMetadata(String section, String key) {
        metadata.clearMetadata(section, key);
    }

    public Metadata getMetadata() {
        return metadata;
    }

    @Override
    public Map<String, Object> getMetadata(String section) {
        return metadata.getMetadata(section);
    }

    @Override
    public Object getMetadata(String section, String key) {
        return metadata.getMetadata(section, key);
    }

    @Override
    public User getUser() {
        return user;
    }

    @Override
    public void setUser(String id, String email, String name) {
        user = new User(id, email, name);
    }
}
